package org.nimi317.web_gis.model;

import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.nimi317.web_gis.data.AreaCodeList;
import org.nimi317.web_gis.entity.BaseQuality;
import org.nimi317.web_gis.entity.Model;
import org.nimi317.web_gis.entity.QualityModel;
import org.nimi317.web_gis.exception.E;
import org.nimi317.web_gis.exception.RException;
import org.nimi317.web_gis.service.ModelService;
import org.nimi317.web_gis.service.QualityModelService;
import org.nimi317.web_gis.utils.QualityUtils;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.function.Function;

/**
 * 空气质量训练模型
 *
 * @author thunderobot
 */
@Slf4j
@Component
public class AirQualityModel {

    private final Map<String, PredictModel> models = new HashMap<>();

    private final ModelService modelService;

    public AirQualityModel(QualityModelService qualityModelService, ModelService modelService) {
        this.modelService = modelService;
        List<QualityModel> list = qualityModelService.list();
        List<Model> modelList = modelService.list();
        list.forEach(item -> {
            Optional<Model> first = modelList.stream().filter(m -> m.getId().equals(item.getModelId())).findFirst();
            first.ifPresentOrElse(m -> {
                models.put(item.getId(), new PredictModel(m));
            }, () -> {
                log.error("模型不存在");
            });
        });
    }

    public void addModel(QualityModel qualityModel) {
        Integer id = qualityModel.getModelId();
        Model model = modelService.getById(id);
        if (ObjectUtil.isNull(model)) {
            log.error("id：{}模型不存在", id);
            throw new RException(E.ModelNotFoundException);
        }
        this.models.put(qualityModel.getId(), new PredictModel(model));
    }

    public boolean hasModel(String id) {
        return this.models.containsKey(id);
    }

    public void removeModel(String id) {
        this.models.remove(id);
    }

    /**
     * 转化为 [co,no2,o3，pm10,pm25,so2,pm25_24H,pm10_24H,o3_8H,so2_24H]
     *
     * @param id      模型id
     * @param quality 空气质量
     * @return 预测值
     */
    public List<BaseQuality> preparedData(String id, List<? extends BaseQuality> quality) {
        int size = quality.size();
        double[][] result = new double[size][10];
        //对二维数组进行异常值填充
        for (int i = 0; i < size; i++) {
            result[i][0] = parseOrInsert(quality, i, BaseQuality::getCo);
            result[i][1] = parseOrInsert(quality, i, BaseQuality::getNo2);
            result[i][2] = parseOrInsert(quality, i, BaseQuality::getO3);
            result[i][3] = parseOrInsert(quality, i, BaseQuality::getPm10);
            result[i][4] = parseOrInsert(quality, i, BaseQuality::getPm25);
            result[i][5] = parseOrInsert(quality, i, BaseQuality::getSo2);
            result[i][6] = parseOrInsert(quality, i, BaseQuality::getPm25H);
            result[i][7] = parseOrInsert(quality, i, BaseQuality::getPm10H);
            result[i][8] = parseOrInsert(quality, i, BaseQuality::getO3H);
            result[i][9] = parseOrInsert(quality, i, BaseQuality::getSo2H);
        }
        double[][] predict = models.get(id).predict(result);
        List<BaseQuality> dataList = new LinkedList<>(quality);
        for (double[] floats : predict) {
            BaseQuality baseQuality = new BaseQuality();
            baseQuality.setCo(parseString(floats[0]));
            baseQuality.setNo2(parseString(floats[1]));
            baseQuality.setO3(parseString(floats[2]));
            baseQuality.setPm10(parseString(floats[3]));
            baseQuality.setPm25(parseString(floats[4]));
            baseQuality.setSo2(parseString(floats[5]));
            baseQuality.setPm25H(parseString(floats[6]));
            baseQuality.setPm10H(parseString(floats[7]));
            baseQuality.setO3H(parseString(floats[8]));
            baseQuality.setSo2H(parseString(floats[9]));
            new QualityUtils<>(baseQuality).exeAqi();
            dataList.add(baseQuality);
        }
        return dataList;
    }

    private String parseString(double value) {
        return String.format("%.2f", value);
    }


    private float parseOrInsert(List<? extends BaseQuality> data, int index, Function<BaseQuality, String> getter) {
        try {
            return Float.parseFloat(getter.apply(data.get(index)));
        } catch (NumberFormatException e) {
            return fill(data, index, getter);
        }
    }

    private float fill(List<? extends BaseQuality> result, int i, Function<BaseQuality, String> method) {
        float up = search(result, i, method, true);
        float down = search(result, i, method, false);
        if (up == 0 && down == 0) {
            throw new RException(E.AirQualityPredictException);
        }
        return (up + down) / ((up == 0 || down == 0) ? 1 : 2);
    }

    private float search(List<? extends BaseQuality> data, int i, Function<BaseQuality, String> method, boolean up) {
        String applied;
        while (true) {
            applied = method.apply(data.get(i));
            if (!AreaCodeList.abnormalArray.contains(applied)) {
                break;
            }
            if (up) {
                i--;
            } else {
                i++;
            }
            if (i < 0 || i >= data.size()) {
                return 0;
            }
        }
        return Float.parseFloat(applied);
    }
}
